/*
<div class='app'>
    {{count}}
    <button>{{count}}</button>
</div>
new VNode('div', {class: 'app'}, [
    new VNode(undefined, undefined, 1),
    new VNode('button',{}, [
        new VNode(undefined, undefined, 1)
    ]),
])
*/
import { isUndef } from '../help';
import {hooks} from './hooks/index'
import { appendChild, insertBefore } from './node-ops';
import {toggleObserving} from '../observer/index'
// 用于 props 更新标志位置， 只有调用prepatch时会为 true, 这样修改值就不会报警告了
export let isUpdatingComponent = false;
export class VNode{
    constructor(context, tag, data, children, isComment, ctorOptions){
        /*
        这样基本就可以表示所有的dom了
        */
        this.context = context;
        if (tag){
            this.tag = tag;
            this.data = data;
            this.children = children;
            if (ctorOptions){
                this.ctorOptions = ctorOptions;
            }
        }else if (isComment){
            this.isComment = true;
            this.comment = children;
        }else{
            this.text = children;
        }
    }
}
export function createElement(context, tag, data, children){
    return new VNode(context, tag, data, children);
}
// 注释节点
const emptyVnode = createElement(undefined, undefined, undefined, '', true);
function invokeCreateHooks(vnode){
    for (let i=0; i<hooks.create.length; i++){
        hooks.create[i](emptyVnode, vnode);
    }
}

function createComponent(vnode, parentElm){
    if (isUndef(vnode.ctorOptions))return;
    // 初始化组件
    const {ctorOptions: {Ctor, CtorOptions, propData, listeners}} = vnode;
    /*

    本身的options, 还有来自父级的propData
    new Vue(options)
    */
   
    const options = {
        _isComponent: true,
        _parent: vnode.ctorOptions,
        _parentVnode: vnode, // slotScope 得访问父级的data
        options: CtorOptions
    }
    const childComp = new Ctor(options);
    childComp.$mount()
    vnode.componentInstance = childComp;

    vnode.data.hooks = {};
    vnode.data.hooks.prepatch = function updateComponent(oldVnode, vnode){
        vnode.componentInstance = oldVnode.componentInstance;
        vnode.data = oldVnode.data;
        isUpdatingComponent = true;
        const {ctorOptions: {propData}} = vnode;
        // 这里 observe 过的值都是不可被枚举, 因此使用这个方法枚举。
        // vue是在 initProps的时候将 所有的props的key都保存了下。然后这里可以直接取了
        const keys = Object.getOwnPropertyDescriptors(oldVnode.componentInstance._props);
        toggleObserving(false)
        for (let key in keys){
            // 主动赋值
            oldVnode.componentInstance._props[key] = propData[key];
        }
        toggleObserving(true)
        // vnode.
        // console.log(oldVnode, vnode);
        isUpdatingComponent = false;

    };
    // 设置子组件的挂载点
    // vm.$el = 
    appendChild(parentElm,childComp._vnode.elm);
    invokeCreateHooks(vnode);
    // insertBefore(parentElm, childComp._vnode.elm, null)
    return true;
}

// 根据vnode创建真实dom
export function createElm(vnode, parentElm){
    // 如果是创建的组件
    if (createComponent(vnode, parentElm)){
        return;
    }
    const {tag, data, children, text, isComment, comment} = vnode;
    let dom;
    if (tag){
        // div,p 元素
        dom = document.createElement(tag);
        // 为了后续diff时候可以通过vnode直接修改对应的dom
        vnode.elm = dom;
        if (vnode.data){
            invokeCreateHooks(vnode);
        }
        // 递归创建
        children && children.forEach(childVnode=>{
            const child = createElm(childVnode, dom);
            if (child){
                appendChild(dom, child);
            }
        });
    }else if (isComment){
        dom = document.createComment(comment);
        vnode.elm = dom;
    }else{
        dom = document.createTextNode(text);
        vnode.elm =  dom;
    }
    return dom;
}
